package cn.dcpnet.feign;

import cn.dcpnet.DFeignRequestManager;
import cn.dcpnet.IDFeignRequest;
import cn.dcpnet.annotation.DFeign;
import cn.dcpnet.annotation.DFeignInterface;
import org.springframework.beans.factory.FactoryBean;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author: 代国平
 * @since: 2025/1/6 15:22
 */
public class DFeignFactoryBean<T> implements FactoryBean<T> {

    @Resource
    private DFeignRequestManager requestManager;



    private final Class<T> feignInterfaceClass;

    public DFeignFactoryBean(Class<T> feignInterfaceClass) {
        this.feignInterfaceClass = feignInterfaceClass;
    }

    /**
     * Return an instance (possibly shared or independent) of the object
     * managed by this factory.
     * <p>As with a {@link }, this allows support for both the
     * Singleton and Prototype design pattern.
     * <p>If this FactoryBean is not fully initialized yet at the time of
     * the call (for example because it is involved in a circular reference),
     * throw a corresponding {@link }.
     * <p>As of Spring 2.0, FactoryBeans are allowed to return {@code null}
     * objects. The factory will consider this as normal value to be used; it
     * will not throw a FactoryBeanNotInitializedException in this case anymore.
     * FactoryBean implementations are encouraged to throw
     * FactoryBeanNotInitializedException themselves now, as appropriate.
     *
     * @return an instance of the bean (can be {@code null})
     * @throws Exception in case of creation errors
     * @see
     */
    public T getObject() throws Exception {
        return (T) Proxy.newProxyInstance(feignInterfaceClass.getClassLoader(), new Class[] {feignInterfaceClass},
                (Object proxy, Method method, Object[] args) -> {
                    if (Object.class.equals(method.getDeclaringClass())) {
                        return method.invoke(this, args);
                    } else {
                        if (!method.isAnnotationPresent(DFeignInterface.class)) {
                            return null;
                        }

                        DFeign clientAnnotation = feignInterfaceClass.getAnnotation(DFeign.class);
                        DFeignInterface methodAnnotation = method.getAnnotation(DFeignInterface.class);
                        Class<?> returnType = method.getReturnType();

                        String module = clientAnnotation.service();
                        long timeout = methodAnnotation.timeout();
                        String api = methodAnnotation.interface_name();

                        String deviceCode = (String) args[0];
                        String token = (String) args[1];
                        IDFeignRequest<?> data = args.length == 3 ? (IDFeignRequest<?>) args[2] : null;
                        return returnType.cast(requestManager.sendMessage(deviceCode, module, api, token, timeout, data));
                    }
        }
        );
    }

    /**
     * Return the type of object that this FactoryBean creates,
     * or {@code null} if not known in advance.
     * <p>This allows one to check for specific types of beans without
     * instantiating objects, for example on autowiring.
     * <p>In the case of implementations that are creating a singleton object,
     * this method should try to avoid singleton creation as far as possible;
     * it should rather estimate the type in advance.
     * For prototypes, returning a meaningful type here is advisable too.
     * <p>This method can be called <i>before</i> this FactoryBean has
     * been fully initialized. It must not rely on state created during
     * initialization; of course, it can still use such state if available.
     * <p><b>NOTE:</b> Autowiring will simply ignore FactoryBeans that return
     * {@code null} here. Therefore it is highly recommended to implement
     * this method properly, using the current state of the FactoryBean.
     *
     * @return the type of object that this FactoryBean creates,
     * or {@code null} if not known at the time of the call
     */
    public Class<?> getObjectType() {
        return feignInterfaceClass;
    }
}
